import sys
import json
import re
from kconfiglib import (
    Kconfig,
    Symbol,
    Choice,
    MENU,
    COMMENT,
    MenuNode,
    BOOL,
    TRISTATE,
    STRING,
    INT,
    HEX,
    AND,
    OR,
    expr_str,
    expr_value,
    split_expr,
    standard_sc_expr_str,
    TRI_TO_STR,
    TYPE_TO_STR,
    standard_kconfig,
    standard_config_filename,
)

def _menu_path_info(node, _kconf):
    # Returns a string describing the menu path leading up to 'node'

    path = ""

    while node.parent is not _kconf.top_node:
        node = node.parent

        # Promptless choices might appear among the parents. Use
        # standard_sc_expr_str() for them, so that they show up as
        # '<choice (name if any)>'.
        path = (
            " -> "
            + (node.prompt[0] if node.prompt else standard_sc_expr_str(node.item))
            + path
        )

    return "(Top)" + path


def _split_expr_info(expr, level):
    # Returns a string with 'expr' split into its top-level && or || operands,
    # with one operand per line, together with the operand's value. This is
    # usually enough to get something readable for long expressions. A fancier
    # recursive thingy would be possible too.
    #
    # level:
    #   in Kconfig level.

    if len(split_expr(expr, AND)) > 1:
        split_op = AND
        op_str = "&&"
    else:
        split_op = OR
        op_str = "||"

    s = ""
    for i, term in enumerate(split_expr(expr, split_op)):
        s += "{}{} {}".format(
            "" if i == 0 else level * " ", "" if i == 0 else op_str, _expr_str(term)
        )

        # Don't bother showing the value hint if the expression is just a
        # single symbol. _expr_str() already shows its value.
        if isinstance(term, tuple):
            s += "(={})".format(TRI_TO_STR[expr_value(term)])

        s += "\n"

    return s


def _expr_str(expr):
    # Custom expression printer that shows symbol values
    return expr_str(expr, _name_and_val_str)


def _name_and_val_str(sc):
    # Custom symbol/choice printer that shows symbol values after symbols

    # Show the values of non-constant (non-quoted) symbols that don't look like
    # numbers. Things like 123 are actually symbol references, and only work as
    # expected due to undefined symbols getting their name as their value.
    # Showing the symbol value for those isn't helpful though.
    if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name):
        if not sc.nodes:
            # Undefined symbol reference
            return "{}(undefined/n)".format(sc.name)

        return "{}(={})".format(sc.name, sc.str_value)

    # For other items, use the standard format
    return standard_sc_expr_str(sc)


def _is_num(name):
    # Heuristic to see if a symbol name looks like a number, for nicer output
    # when printing expressions. Things like 16 are actually symbol names, only
    # they get their name as their value when the symbol is undefined.

    try:
        int(name)
    except ValueError:
        if not name.startswith(("0x", "0X")):
            return False

        try:
            int(name, 16)
        except ValueError:
            return False

    return True


def _defaults_info(sc):
    # Returns a string describing the defaults of 'sc' (Symbol or Choice)

    if not sc.defaults:
        return ""

    s = ""
    #    s = "Default"
    #    if len(sc.defaults) > 1:
    #        s += "s"
    #    s += ":"

    for val, cond in sc.orig_defaults:
        s += "  - "
        if isinstance(sc, Symbol):
            s += _expr_str(val)

            # Skip the tristate value hint if the expression is just a single
            # symbol. _expr_str() already shows its value as a string.
            #
            # This also avoids showing the tristate value for string/int/hex
            # defaults, which wouldn't make any sense.
            if isinstance(val, tuple):
                s += "  (={})".format(TRI_TO_STR[expr_value(val)])
        else:
            # Don't print the value next to the symbol name for choice
            # defaults, as it looks a bit confusing
            s += val.name
        #        s += "\n"

        if cond is not kconf.y:
            s += "    Condition (={}): {}".format(
                TRI_TO_STR[expr_value(cond)], _split_expr_info(cond, 4)
            )

    return s


def _direct_sel_info(sc):
    # Returns a string describing the defaults of 'sc' (Symbol or Choice)

    if not sc.selects:
        return ""

    s = ""
    #    s = "Select"
    #    if len(sc.selects) > 1:
    #        s += "s"
    #    s += ":"
    num = 0
    for val, cond in sc.selects:
        if num != 0:
            s += "  "
        if isinstance(sc, Symbol):
            s += _expr_str(val)

            # Skip the tristate value hint if the expression is just a single
            # symbol. _expr_str() already shows its value as a string.
            #
            # This also avoids showing the tristate value for string/int/hex
            # defaults, which wouldn't make any sense.
            if isinstance(val, tuple):
                s += " (={})".format(TRI_TO_STR[expr_value(val)])
        else:
            # Don't print the value next to the symbol name for choice
            # defaults, as it looks a bit confusing
            s += val.name
        #        s += "\n"
        num += 1
    #        if cond is not kconf.y:
    #            s += "    Depend (={}): {}" \
    #                 .format(TRI_TO_STR[expr_value(cond)],
    #                         _split_expr_info(cond, 4))

    return s


def _direct_dep_info(sc):
    # Returns a string describing the direct dependencies of 'sc' (Symbol or
    # Choice). The direct dependencies are the OR of the dependencies from each
    # definition location. The dependencies at each definition location come
    # from 'depends on' and dependencies inherited from parent items.

    return (
        ""
        if sc.direct_dep is kconf.y
        else "{}".format(_split_expr_info(sc.direct_dep, 2))
    )


def _include_path_info(node):
    if not node.include_path:
        # In the top-level Kconfig file
        return ""

    return "{}".format(
        " -> ".join("{}".format(filename) for filename, linenr in node.include_path)
    )


def node_info(node_name, level, node, kconf):
    dict = {}
    select_str = ""

    if isinstance(node.item, Symbol):
        if node.item.config_string != "":
            config_string = node.item.config_string
            config_value = node.item.str_value
        else:
            config_string = ""
            config_value = ""
    elif isinstance(node.item, Choice):
        config_string = ""
        config_value = node.item.str_value
    else:
        config_string = ""
        config_value = ""

    if kconf.top_node == node:
        top_node_flag = 0
    else:
        top_node_flag = 1

    if top_node_flag == 0:
        dict["Name"] = str(node_name).split(" ", 1)[1]
        dict["Config_string"] = config_string
        dict["Level"] = level
        dict["Type"] = str(node_name).split(" ", 1)[0]
        dict["Config_value"] = config_value
        dict["Include_path_info"] = _include_path_info(node)
        dict["Local"] = node.filename
        dict["Menu_path_info"] = ""
        dict["Parent"] = ""
        dict["Depends"] = ""
        dict["Defaults"] = ""
        dict["Result_Value"] = ""
        dict["Select"] = ""
    else:
        node_parent = (
            node.parent.item.name
            if isinstance(node.parent.item, Symbol)
            else node.parent.prompt[0]
        )
        depends_str = (
            _direct_dep_info(node.item)
            if isinstance(node.item, Symbol) or isinstance(node.item, Choice)
            else ""
        )
        result_str = (
            TRI_TO_STR[expr_value(node.item.direct_dep)]
            if (isinstance(node.item, Symbol) or isinstance(node.item, Choice))
            and node.item.direct_dep is not kconf.y
            else ""
        )
        # TODO: Choice存在一个特别的字段：Symbol currectly...
        defaults_str = (
            _defaults_info(node.item)
            if isinstance(node.item, Symbol) or isinstance(node.item, Choice)
            else ""
        )
        # if config_value != 'n':
        select_str = (
            _direct_sel_info(node.item) if isinstance(node.item, Symbol) else ""
        )
        dict["Name"] = str(node_name).split(" ", 1)[1]
        dict["Config_string"] = config_string.replace("\n", "")
        dict["Level"] = level
        dict["Type"] = str(node_name).split(" ", 1)[0]
        dict["Local"] = node.filename
        dict["Include_path_info"] = _include_path_info(node)
        dict["Menu_path_info"] = _menu_path_info(node, kconf)
        dict["Parent"] = node_parent
        dict["Depends"] = depends_str.replace("choice", node_parent)
        dict["Defaults"] = defaults_str
        dict["Result_Value"] = result_str
        dict["Config_value"] = config_value
        dict["Select"] = select_str
    node_list.append(dict)

def item_info(node, level, kconf):
    while node:
        if isinstance(node.item, Symbol):
            node_info("config " + node.item.name, level, node, kconf)

        elif isinstance(node.item, Choice):
            node_info("choice {}".format(node.prompt[0]), level, node, kconf)

        elif node.item == MENU:
            node_info('menu "{}"'.format(node.prompt[0]), level, node, kconf)

        elif node.item == COMMENT:
            node_info('comment "{}"'.format(node.prompt[0]), level, node, kconf)

        if node.list:
            item_info(node.list, level + 1, kconf)

        node = node.next

if __name__ == "__main__":
    node_list = []
    kconf = Kconfig(sys.argv[1], warn=False)
    kconf.load_config(".config")
    item_info(kconf.top_node, 0, kconf)

    with open("kconfig.json", "w") as outfile:
        data = json.dump(node_list, outfile, indent=4, separators=(",", ": "))

